package online.keepon.document.aspect;

/**
 * @author top
 * @version 1.0.0
 * @desc
 * @date 2020/11/25
 */

import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.SneakyThrows;
import online.keepon.document.util.DateUtil;
import online.keepon.document.util.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbMakerConfigException;
import org.lionsoul.ip2region.DbSearcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

@Aspect
@Component
public class RequestLogAspect {
    private final static Logger log = LoggerFactory.getLogger(RequestLogAspect.class);
    private final TaskExecutor taskExecutor;

    private static DbSearcher searcher;

    static {
        try {
            InputStream inputStream = new ClassPathResource("ip2region.db").getInputStream();
            searcher = new DbSearcher(new DbConfig(), inputStream.readAllBytes());
        } catch (DbMakerConfigException | IOException e) {
            e.printStackTrace();
        }
    }

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public RequestLogAspect(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    @Pointcut("execution(public * online.keepon.document.web..*(..))")
    public void requestServer() {
    }

    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIp(IpUtils.getIp());
        requestInfo.setUrl(IpUtils.getRequest().getRequestURL().toString());
        requestInfo.setHttpMethod(IpUtils.getRequest().getMethod());
        requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName()));
        requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
        if (log.isDebugEnabled()) {
            requestInfo.setResult(result);
        }
        requestInfo.setSuccess("成功");
        requestInfo.setExecuteTime(System.currentTimeMillis() - start);
        requestInfo.setMessage("");
        requestInfo.setExceptionStacks(null);
        requestInfo.setCreateTime(DateUtil.toEpochSecond(LocalDateTime.now()));
        DataBlock dataBlock = searcher.memorySearch(requestInfo.getIp());
        requestInfo.setLocation(dataBlock.getRegion());
        log.info("--------------------------------------------------------------------------------");
        log.info(" 请求信息 : {} ", JSON.toJSONString(requestInfo));
        log.info("----------------------------------- ----------------------------------------------");
        //  save(requestInfo);
        return result;
    }


    @SneakyThrows
    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIp(IpUtils.getIp());
        requestInfo.setUrl(IpUtils.getRequest().getRequestURL().toString());
        requestInfo.setHttpMethod(IpUtils.getRequest().getMethod());
        requestInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()));
        requestInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
        requestInfo.setMessage(e.getMessage());
        requestInfo.setSuccess("失败");
        requestInfo.setExecuteTime(0L);
        requestInfo.setCreateTime(DateUtil.toEpochSecond(LocalDateTime.now()));
        DataBlock dataBlock = searcher.memorySearch(requestInfo.getIp());
        requestInfo.setLocation(dataBlock.getRegion());
        List<String> list = new ArrayList<>();
        for (StackTraceElement traceElement : e.getStackTrace()) {
            String className = traceElement.getClassName();
            if (className.contains("online.keepon.document")) {
                String exception = className + "." + traceElement.getMethodName() + "(" + className.substring(className.lastIndexOf(".")).replace(".", "") + ".java:" + traceElement.getLineNumber() + ")";
                if (!exception.contains("$$")) {
                    list.add(exception);
                }
            }
        }
        requestInfo.setExceptionStacks(list);
        log.info("---------------------------------------------------------------------------------");
        log.info(" 异常信息 : {}", JSON.toJSONString(requestInfo));
        log.info("---------------------------------------------------------------------------------");
        //    save(requestInfo);
    }

    private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        Object[] paramValues = proceedingJoinPoint.getArgs();
        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], value);
        }
        return requestParams;
    }

    public void save(RequestInfo requestInfo) {
        CompletableFuture.runAsync(() -> {
            String sql = "insert into request_log(username,ip,location,create_time, http_method,  method_name,url,  execute_time,success, message, stack_trace)value(?,?,?,?,?,?,?,?,?,?,?)";
            jdbcTemplate.update(sql, "", requestInfo.ip, requestInfo.getLocation(), requestInfo.getCreateTime(), requestInfo.httpMethod, requestInfo.getClassMethod(), requestInfo.getUrl(), requestInfo.getExecuteTime(), requestInfo.getSuccess(), requestInfo.getMessage(), requestInfo.getExceptionStacks());
        }, taskExecutor);
    }

    @Data
    private static class RequestInfo {
        private String ip;
        private String location;
        private String url;
        private String httpMethod;
        private String classMethod;
        private Object requestParams;
        private String success;
        private Object result;
        private Long executeTime;
        private String message;
        private List<String> exceptionStacks;
        private Long createTime;
    }
}
